using System;
using System.Collections.Generic;
using log4net.Core;
using Quiki.ExpressionParsers;
using Quiki.Querying;
using Wintellect.PowerCollections;

namespace Quiki.Templates
{
    public class ListTemplate : NVelocityBasedWikiTemplate
    {
        public ListTemplate (
            ITemplatingEngine templatingEngine, 
            IQueryParser queryParser,
            ILogger logger, 
            DocumentProcessorSettings documentProcessorSettings)
            : base (templatingEngine, logger, documentProcessorSettings)
        {
            this.queryParser = queryParser;
        }

        public virtual bool IncludeOwnerDocumentInTheList
        {
            get { return true; }
        }

        public const string TemplateId = "list";

        public override string TemplateName
        {
            get { return TemplateId; }
        }

        public override void Expand (
            WikiTemplateElement element, 
            WikiDocument document, 
            DocumentGraph documentGraph)
        {
            Set<WikiDocument> documents = FindDocuments(element, documentGraph);
            RemoveOwnerDocumentIfRequested(document, documents);
            List<WikiDocument> sortedDocuments = SortDocuments(documents);
            GenerateDocumentLinks(element, sortedDocuments, documentGraph);
        }

        private Set<WikiDocument> FindDocuments(
            WikiTemplateElement element, 
            DocumentGraph documentGraph)
        {
            Set<WikiDocument> documents = new Set<WikiDocument>();

            foreach (string templateParameter in element.TemplateParameters)
            {
                if (templateParameter.StartsWith("#query", StringComparison.OrdinalIgnoreCase))
                    ProcessQuery(templateParameter, documentGraph, documents);
                else
                    ProcessDocumentReference(templateParameter, documentGraph, documents);
            }

            return documents;
        }

        private void ProcessQuery(string templateParameter, DocumentGraph documentGraph, Set<WikiDocument> documents)
        {
            string queryText = templateParameter.Substring(6);
            Query query = null;
            try
            {
                query = queryParser.Parse(queryText);
            }
            catch (ArgumentException ex)
            {
                Logger.Log(Level.Warn, "Error parsing the query '{0}': {1}", queryText, ex.Message);
            }

            if (query != null)
            {
                IEnumerable<WikiDocument> foundDocuments = query.Run(documentGraph);
                documents.AddMany(foundDocuments);
            }
        }

        private static void ProcessDocumentReference(string templateParameter, DocumentGraph documentGraph, Set<WikiDocument> documents)
        {
            WikiDocReference reference = new WikiDocReference(templateParameter);
            WikiDocument foundDocuments = documentGraph.FindDocument(reference);

            if (foundDocuments == null)
                foundDocuments = new WikiDocument(reference, null);

            documents.Add(foundDocuments);
        }

        private void RemoveOwnerDocumentIfRequested(
            WikiDocument ownerDocument,
            Set<WikiDocument> documents)
        {
            if (false == IncludeOwnerDocumentInTheList)
                documents.Remove(ownerDocument);
        }

        private static List<WikiDocument> SortDocuments(IEnumerable<WikiDocument> documents)
        {
            List<WikiDocument> sortedDocuments = new List<WikiDocument>(documents);
            sortedDocuments.Sort(
                (a, b) => string.Compare(a.Title, b.Title, StringComparison.CurrentCultureIgnoreCase));
            return sortedDocuments;
        }

        private static void GenerateDocumentLinks(
            WikiTemplateElement element, 
            IEnumerable<WikiDocument> sortedDocuments,
            DocumentGraph documentGraph)
        {
            List<WikiInternalLinkElement> links = new List<WikiInternalLinkElement>();
            foreach (WikiDocument linkedDocument in sortedDocuments)
            {
                WikiInternalLinkElement link = new WikiInternalLinkElement();
                link.Exists = documentGraph.HasDocument(linkedDocument.Reference);
                link.Reference = linkedDocument.Reference;
                links.Add(link);
            }

            element.Properties["links"] = links;

            foreach (WikiInternalLinkElement link in links)
                element.ChildElements.Add (link);
        }

        private readonly IQueryParser queryParser;
    }
}
